package jeme;

import static jeme.StdlibProcedure.*;
import jeme.lang.SchemeObject;
import jeme.lang.SchemePair;
import jeme.lang.SchemeString;
import jeme.lang.SchemeVector;

/**
 * All Scheme modules should extend this class. Semantically it doesn't
 * really make sense that a module <em>is-a</em> procedure, but because
 * of how we implement first-class procedures in Java, this is 
 * nevertheless the case.
 *
 * @author Erik Silkensen (silkense@colorado.edu)
 * @version Aug 12, 2009
 */
public abstract class Module extends Procedure
{
    static
    {
        initializeEnvironment();
    }
    
    /**
     * Initialize the default environment for all modules.
     */
    private static void initializeEnvironment()
    {
        Environment.DEFAULT.sput("display", DISPLAY);
        Environment.DEFAULT.sput("current-output-port", CURRENT_OUTPUT_PORT);
        Environment.DEFAULT.sput("+", ADD);
        Environment.DEFAULT.sput("-", SUBTRACT);
        Environment.DEFAULT.sput("*", MULTIPLY);
        Environment.DEFAULT.sput("/", DIVIDE);
        Environment.DEFAULT.sput("=", NUM_EQ);
        Environment.DEFAULT.sput("cons", CONS);
        Environment.DEFAULT.sput("car", CAR);
        Environment.DEFAULT.sput("cdr", CDR);
        Environment.DEFAULT.sput("list", LIST);
        Environment.DEFAULT.sput("pair?", PAIRQ);
        Environment.DEFAULT.sput("list?", LISTQ);
        Environment.DEFAULT.sput("eq?", EQ);
        Environment.DEFAULT.sput("null?", NULLQ);
        Environment.DEFAULT.sput("newline", NEWLINE);
        Environment.DEFAULT.sput("length", LENGTH);
        Environment.DEFAULT.sput("real-part", REAL_PART);
        Environment.DEFAULT.sput("imag-part", IMAG_PART);
        Environment.DEFAULT.sput("angle", ANGLE);
        Environment.DEFAULT.sput("magnitude", MAGNITUDE);
        Environment.DEFAULT.sput("log", LOG);
        Environment.DEFAULT.sput("sqrt", SQRT);
        Environment.DEFAULT.sput("sin", SIN);
        Environment.DEFAULT.sput("cos", COS);
        Environment.DEFAULT.sput("tan", TAN);
        Environment.DEFAULT.sput("asin", ASIN);
        Environment.DEFAULT.sput("acos", ACOS);
        Environment.DEFAULT.sput("atan", ATAN);
        Environment.DEFAULT.sput("<", LT);
        Environment.DEFAULT.sput(">", GT);
        Environment.DEFAULT.sput("<=", LTE);
        Environment.DEFAULT.sput(">=", GTE);
        Environment.DEFAULT.sput("equal?", EQUAL);
        Environment.DEFAULT.sput("vector-ref", VECREF);
        Environment.DEFAULT.sput("system", SYSTEM);
        Environment.DEFAULT.sput("vector-length", VECLEN);
        Environment.DEFAULT.sput("string-append",STRING_APPEND);
    }
       
    /**
     * Creates a procedure within this module with a specified arity
     * and index value. The default environment is used.
     * 
     * @param arity  the arity of this procedure
     * @param index  the index value of this procedure
     */
    public Module(int arity, int index)
    {
        super(arity, index);
    }

    /**
     * Creates a procedure within this module with a specified arity, 
     * index value, and environment.
     * 
     * @param arity  the arity of this procedure
     * @param index  the index value of this procedure
     * @param env  the parent of the environment to evaluate this
     * procedure in
     */
    public Module(int arity, int index, Environment env)
    {
        super(arity, index, env);
    }

    /**
     * Creates a procedure within this module with a specified arity
     * range and index value. The default environment is used. The 
     * arity arguments can be used to specify a range of valid argument
     * values (e.g. <code>new Module(1, 2)</code>).
     * 
     * @param arity1  an arity of this procedure
     * @param arity2  an arity of this procedure
     * @param index  the index value of this procedure
     */
    public Module(int arity1, int arity2, int index)
    {
        super(arity1, arity2, index);
    }

    /**
     * Creates a procedure within this module with a specified arity
     * range, index value, and environment. The arity arguments can be
     * used to specify a range of valid argument values (e.g. <code>new
     * Module(1, 2)</code>).
     * 
     * @param arity1  an arity of this procedure
     * @param arity2  an arity of this procedure
     * @param index  the index value of this procedure
     * @param env  the parent of the environment to evaluate this
     * procedure in
     */
    public Module(int arity1, int arity2, int index, Environment env)
    {
        super(arity1, arity2, index, env);
    }
    
    /**
     * 
     * @param args
     * @return
     */
    protected static SchemeObject toVector(String[] args)
    {
        return toVector(args, 0);
    }
    
    /**
     * 
     * @param args
     * @param start
     * @return
     */
    protected static SchemeObject toVector(String[] args, int start)
    {
        SchemeString[] sargs = new SchemeString[args.length - start];

        for (int i = start; i < args.length; i++) {
            sargs[i - start] = new SchemeString(args[i]);
        }

        return new SchemeVector(sargs);
    }
    
    /**
     * 
     * @param args
     * @return
     */
    protected static SchemeObject toVector(SchemeObject[] args)
    {
        return toVector(args, 0);
    }
    
    /**
     * 
     * @param args
     * @param start
     * @return
     */
    protected static SchemeObject toVector(SchemeObject[] args, int start)
    {
        SchemeObject[] oargs = new SchemeObject[args.length - start];

        for (int i = start; i < args.length; i++) {
            oargs[i - start] = args[i];
        }

        return new SchemeVector(oargs);
    }
    
    /**
     * 
     * @param args
     * @return
     */
    protected static SchemeObject toList(String[] args)
    {
        return toList(args, 0);
    }
    
    /**
     * 
     * @param args
     * @param start
     * @return
     */
    protected static SchemeObject toList(String[] args, int start)
    {
        if (args.length - start == 0) {
            return SchemePair.EMPTY_LIST;
        }
        
        SchemePair list = new SchemePair(new SchemeString(args[start]), null);
        
        SchemePair it = list;
        for (int i = start + 1; i < args.length; i++) {
            SchemePair el = new SchemePair(new SchemeString(args[i]), null);
            it.setCdr(el);
            it = el;
        }
        it.setCdr(SchemePair.EMPTY_LIST);
        
        return list;
    }
    
    /**
     * 
     * @param args
     * @return
     */
    protected static SchemeObject toList(SchemeObject[] args)
    {
        return toList(args, 0);
    }
    
    /**
     * 
     * @param args
     * @param start
     * @return
     */
    protected static SchemeObject toList(SchemeObject[] args, int start)
    {
        if (args.length - start == 0) {
            return SchemePair.EMPTY_LIST;
        }
        
        SchemePair list = new SchemePair(args[start], null);
        
        SchemePair it = list;
        for (int i = start + 1; i < args.length; i++) {
            SchemePair el = new SchemePair(args[i], null);
            it.setCdr(el);
            it = el;
        }
        it.setCdr(SchemePair.EMPTY_LIST);
        
        return list;
    }
}
